/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

package bsp.loader
{
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.MovieClip;
	import flash.display.SimpleButton;
	import flash.system.ApplicationDomain;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import flash.events.*;
	
	public class LoaderBase
	{
		private var o_loader		: Loader;
		private var o_urlloader		: URLLoader;
		private var o_request		: URLRequest;
		private var o_domain		: ApplicationDomain;
		private var is_mclib		: Boolean = false;
		
		private var f_init			: Function;
		private var b_init			: Boolean = false;
		private var f_complete		: Function;
		private var b_complete		: Boolean = false;
		private var f_open			: Function;
		private var b_open			: Boolean = false;
		private var f_unload		: Function;
		private var b_unload		: Boolean = false;
		private var f_http_status	: Function;
		private var b_http_status	: Boolean = false;
		private var f_io_error		: Function;
		private var b_io_error		: Boolean = false;
		private var f_progress		: Function;
		private var b_progress		: Boolean = false;
		private var f_security_error	: Function;
		private var b_security_error	: Boolean = false;
		
		// SWF properties
		private var swf_content		: ByteArray;
		private var swf_version		: int;
		private var swf_frame_rate	: int;
		private var swf_frame_count	: int;
		private var swf_compressed	: Boolean = false;
		private var swf_length		: int;
		private var swf_classes		: Array;
		
		/* Constructor */
		public function LoaderBase() : void
		{
			this.o_loader = new Loader();
			this.o_urlloader = new URLLoader();
			
			this.o_loader.contentLoaderInfo.addEventListener(Event.INIT, this._on_init);
			this.o_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, this._on_complete);
			this.o_loader.contentLoaderInfo.addEventListener(Event.OPEN, this._on_open);
			this.o_loader.contentLoaderInfo.addEventListener(Event.UNLOAD, this._on_unload);
			this.o_loader.contentLoaderInfo.addEventListener(HTTPStatusEvent.HTTP_STATUS, this._on_http_status);
			this.o_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, this._on_io_error);
			this.o_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, this._on_progress);
			
			this.o_urlloader.addEventListener(Event.COMPLETE, this._on_complete);
			this.o_urlloader.addEventListener(Event.OPEN, this._on_open);
			this.o_urlloader.addEventListener(HTTPStatusEvent.HTTP_STATUS, this._on_http_status);
			this.o_urlloader.addEventListener(IOErrorEvent.IO_ERROR, this._on_io_error);
			this.o_urlloader.addEventListener(ProgressEvent.PROGRESS, this._on_progress);
			this.o_urlloader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this._on_security_error);
			
			return;
		}
		
		public function load(request_url : String, i : Boolean = false) : void
		{
			this.o_request = new URLRequest(request_url);
			
			if (i)
			{
				// SWF or Image
				this.o_loader.load(this.o_request);
			}
			
			else
			{
				// Other files
				this.o_urlloader.load(this.o_request);
			}
			
			// If target is a remote swf contains movieclips, load the applicationDomain
			this.is_mclib = i;
			
			return;
		}
		
		public function close() : void
		{
			this.o_loader.close();
			
			return;
		}
		
		/* Return content */
		public function data() : *
		{
			if (this.is_mclib)
			{
				return new ByteArray();
			}
			
			else
			{
				return this.o_urlloader.data;
			}
		}
		
		/* Return a movieclip if available */
		public function mc(mc_name : String) : MovieClip
		{
			var ret : MovieClip;
			
			if (this.is_mclib)
			{
				if (this.o_domain.hasDefinition(mc_name))
				{
					var c : Class = this.o_domain.getDefinition(mc_name) as Class;
					ret = new c() as MovieClip;
				}
				
				else
				{
					trace('Load MovieClip', mc_name, 'error');
					ret = new MovieClip();
				}
			}
			
			else
			{
				ret = new MovieClip();
			}
			
			return ret;
		}
		
		/* Return a simplebutton if available */
		public function btn(btn_name : String) : SimpleButton
		{
			var ret : SimpleButton;
			
			if (this.is_mclib)
			{
				if (this.o_domain.hasDefinition(btn_name))
				{
					var c : Class = this.o_domain.getDefinition(btn_name) as Class;
					ret = new c() as SimpleButton;
				}
				
				else
				{
					trace('Load SimpleButton', btn_name, 'error');
					ret = new SimpleButton();
				}
			}
			
			else
			{
				ret = new SimpleButton();
			}
			
			return ret;
		}
		
		// Class list
		public function class_list() : Array
		{
			if (this.is_mclib)
			{
				return this.swf_classes;
			}
			
			else
			{
				return [];
			}
		}
		
		// Sets
		public function set m_init(l : Function) : void
		{
			this.f_init = l;
			this.b_init = true;
			
			return;
		}
		
		public function set m_complete(l : Function) : void
		{
			this.f_complete = l;
			this.b_complete = true;
			
			return;
		}
		
		public function set m_open(l : Function) : void
		{
			this.f_open = l;
			this.b_open = true;
			
			return;
		}
		
		public function set m_unload(l : Function) : void
		{
			this.f_unload = l;
			this.b_open = true;
			
			return;
		}
		
		public function set m_http_status(l : Function) : void
		{
			this.f_http_status = l;
			this.b_http_status = true;
			
			return;
		}
		
		public function set m_io_error(l : Function) : void
		{
			this.f_io_error = l;
			this.b_io_error = true;
			
			return;
		}
		
		public function set m_progress(l : Function) : void
		{
			this.f_progress = l;
			this.b_progress = true;
			
			return;
		}
		
		public function set m_security_error(l : Function) : void
		{
			this.f_security_error = l;
			this.b_security_error = true;
			
			return;
		}
		
		// Handlers
		private function _on_init(e : Event) : void
		{
			if (this.b_init)
			{
				this.f_init();
			}
			
			return;
		}
		
		private function _on_complete(e : Event) : void
		{
			if (this.is_mclib)
			{
				this.o_domain = e.target.applicationDomain;
				
				// Parse swf
				this._parse_swf(e.target.bytes);
			}
			
			if (this.b_complete)
			{
				this.f_complete();
			}
			
			return;
		}
		
		private function _on_open(e : Event) : void
		{
			if (this.b_open)
			{
				this.f_open();
			}
			
			return;
		}
		
		private function _on_unload(e : Event) : void
		{
			if (this.b_unload)
			{
				this.f_unload();
			}
			
			return;
		}
		
		private function _on_http_status(e : HTTPStatusEvent) : void
		{
			if (this.b_http_status)
			{
				this.f_http_status();
			}
			
			return;
		}
		
		private function _on_io_error(e : IOErrorEvent) : void
		{
			if (this.b_io_error)
			{
				this.f_io_error();
			}
			
			return;
		}
		
		private function _on_progress(e : ProgressEvent) : void
		{
			if (this.b_progress)
			{
				this.f_progress(e.bytesLoaded, e.bytesTotal);
			}
			
			return;
		}
		
		private function _on_security_error(e : SecurityErrorEvent) : void
		{
			if (this.b_security_error)
			{
				this.f_security_error();
			}
			
			return;
		}
		
		private function _parse_swf(b : ByteArray) : void
		{
			// Parse swf bytearray
			this.swf_classes = new Array();
			
			b.position = 0;
			
			var tag : int;
			tag = b.readUnsignedByte();
			
			if (tag == 0x43)
			{
				this.swf_compressed = true;
			}
			
			b.position += 2;
			this.swf_version = b.readByte();
			
			b.endian = Endian.LITTLE_ENDIAN;
			this.swf_length = b.readUnsignedInt();
			
			this.swf_content = new ByteArray();
			b.readBytes(this.swf_content, 0);
			
			if (this.swf_compressed)
			{
				this.swf_content.uncompress();
			}
			
			this.swf_content.endian = Endian.LITTLE_ENDIAN;
			var nb : int = (((this.swf_content.readByte() >> 3) * 4 - 3) >> 3) + 1;
			this.swf_content.position += nb + 1;
			
			this.swf_frame_rate = this.swf_content.readByte();
			this.swf_frame_count = this.swf_content.readShort();
			var dictionary : Array = this._browse_table();
			
			// Fetch classes
			var criteria			: int = 0x4C;
			var symbol_classes		: Array = dictionary.filter(this._filter);
			var i					: int;
			var count				: int;
			var char				: int;
			var name				: String;
			
			if (symbol_classes.length)
			{
				this.swf_content.position = symbol_classes[0].offset;
				count = this.swf_content.readUnsignedShort();
				
				for (i = 0; i < count; i ++)
				{
					this.swf_content.readUnsignedShort();
					char = this.swf_content.readByte();
					name = new String();
					
					while (char != 0)
					{
						name += String.fromCharCode(char);
						char = this.swf_content.readByte();
					}
					
					this.swf_classes.push(name);
				}
			}
		}
		
		private function _filter(element : TagInfos, index : int, array : Array) : Boolean
		{
			return element.tag == 0x4C;
		}
		
		private function _browse_table() : Array
		{
			var current_tag		: int;
			var step			: int;
			var dictionary		: Array = new Array();
			var infos			: TagInfos;
			
			while (current_tag = ((this.swf_content.readShort() >> 6) & 0x3FF))
			{
				infos = new TagInfos();
				infos.tag = current_tag;
				infos.offset = this.swf_content.position;
				this.swf_content.position -= 2;
				step = this.swf_content.readShort() & 0x3F;
				
				if (step < 0x3F)
				{
					this.swf_content.position += step;
				}
				
				else
				{
					step = this.swf_content.readUnsignedInt();
					infos.offset = this.swf_content.position;
					this.swf_content.position += step;
				}
				
				infos.end_offset = this.swf_content.position;
				dictionary.push(infos);
			}
			
			return dictionary;
		}
	}
}

final class TagInfos
{
	public var offset			: int;
	public var end_offset		: int;
	public var tag				: int;
}